package org.merak.core.text;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.merak.core.annotation.Skip;
import org.merak.core.annotation.Style;
import org.merak.core.text.template.TemplateMap;
import org.merak.core.text.template.TemplateObject;
import org.merak.core.text.template.TemplateProperty;

/**
 * Copyright © 2010-2012 Merak Computing. All rights reserved.<br><br>
 *
 * The Context object holds data about each textification execution like:
 * - user context: locale
 * - property context: style instructions
 * - object context: strategy conversors (dto)
 * - execution context: language textification logics, output buffer, stack control
 *
 * @author fmarinho.
 * @version 1.00.00 - Jul 20, 2012 - fmarinho - initial version.
 *
 * @since MerakCore 1.00.00
 */
public final class Context {

    //~ Static /////////////////////////////////////////////////////////////////////////////////////
	//**********************************************************************************************
	private static final ThreadLocal<Context> instance = new ThreadLocal<Context>();

    //~ Attributes /////////////////////////////////////////////////////////////////////////////////
	//**********************************************************************************************
	private final StringBuilder buffer    = new StringBuilder(256); // Used to concatenate text
	private final List<Object>  stack     = new ArrayList<Object>();    // Used to detect recursion cycles
	private final Styler        styler    = new Styler();

	private       TemplateMap   templates  = null;
	private       Control       controller = null;
	private       Style         style      = null;

    //~ Constructors ///////////////////////////////////////////////////////////////////////////////
	//**********************************************************************************************
	private Context() {
		// Do nothing here
	}

    //~ Methods ////////////////////////////////////////////////////////////////////////////////////
	//**********************************************************************************************
	@Skip
	public static Context getInstance(Control controller,TemplateMap templates) {

		// Get context attached to current thread
		Context context = instance.get();
		if (context == null) instance.set(
			context = new Context()
		);

		// Update factory and templates
		context.controller = controller;
		context.templates  = templates;
		return context;

	}
	//**********************************************************************************************
	/** Returns the style associated to the currently stacked object or null if no style */
	@Skip
	public Style getStyle() {

		return this.style;

	}
	//**********************************************************************************************
	/** Returns the pre-processed template of given class */
	@Skip
	public TemplateObject getTemplate(Class<?> clazz) {

		return this.templates.get(clazz);

	}
	//**********************************************************************************************
	/**
	 * Finds and executes a Textifier based on object's class.
	 * @param object The object to be textified
	 * @throws Exception If the textifier fails to execute.
	 * @since 1.00.00
	 */
	public Context textify(Object object) throws Exception {

		this.controller.execute(object,this);
		return this;

	}
	//**********************************************************************************************
	/**
	 * Finds and executes a Textifier based on the class of the object returned by the getter of the
	 * provided property invoked on the provided object. The context state will change depending on
	 * the annotations of the property.
	 * @param object The object owning the property.
	 * @param p The property whose getter will be called.
	 * @throws Exception If the textifier fails to execute.
	 * @since 1.00.00
	 */
	public Context textify(Object object,TemplateProperty p) throws Exception {

		// Save current state
		Style style = this.style;
		this.style  = p.getStyle();

		// Textify the property
		this.controller.execute(object,p.getGetter(),this);

		// Restore current state
		this.style = style;
		return this;

	}
	//**********************************************************************************************
	/**
	 * Writes a single character based on String.valueOf method. No style is applied.
	 * @param object
	 * @since 1.00.00
	 */
	public Context write(char object) {

		this.buffer.append(object);
		return this;

	}
	//**********************************************************************************************
	/**
	 * Writes an object based on String.valueOf method. No style is applied.
	 * @param object
	 * @since 1.00.00
	 */
	public Context write(Object object) {

		if (this.style == null) {
			this.buffer.append(object);
		}
		else {
			String str = this.styler.style(object,this.style);
			this.buffer.append(str);
		}
		return this;

	}
	//**********************************************************************************************
	/**
	 * Writes a quoted object based on String.valueOf method. No style is applied.
	 * @param object
	 * @since 1.00.00
	 */
	public Context writeQuoted(Object object) {

		this.buffer.append('"');
		if (this.style == null) {
			this.buffer.append(object);
		}
		else {
			String str = this.styler.style(object,this.style);
			str = this.styler.escapeQuote(str);
			this.buffer.append(str);
		}
		this.buffer.append('"');
		return this;

	}
	//**********************************************************************************************
	/**
	 * Writes a date as milliseconds (a long integer). If any style is present, the date will
	 * be styled as string. Since StringTextifier will be called, StringTextifier should never call
	 * this method, or an infinity recursion will start, ending in a java.lang.StackOverflowError.
	 * @throws Exception If the StringTextifier fails to execute
	 * @throws StackOverflowError If the StringTextifier calls this method.
	 * @since 1.00.00
	 */
	public Context write(Date date) throws Exception {

		if (this.style == null || this.style.date().isEmpty()) {
			this.buffer.append( date.getTime() );
		}
		else {
			String str = this.styler.style(date,this.style);
			this.controller.execute(str, this);
		}
		return this;

	}
	//**********************************************************************************************
	/**
	 * Writes a quoted date as milliseconds (a long integer). If any style is present, the date will
	 * be styled as string. Since StringTextifier will be called, StringTextifier should never call
	 * this method, or an infinity recursion will start, ending in a java.lang.StackOverflowError.
	 * @throws Exception If the StringTextifier fails to execute
	 * @throws StackOverflowError If the StringTextifier calls this method.
	 * @since 1.00.00
	 */
	public Context writeQuoted(Date date) throws Exception {

		this.buffer.append('"');
		this.write(date);
		this.buffer.append('"');
		return this;

	}
	//**********************************************************************************************
	/**
	 * Writes a number based on String.valueOf method. If any style is present, the number will be
	 * styled as string. Since StringTextifier will be called, StringTextifier should never call
	 * this method, or an infinity recursion will start, ending in a java.lang.StackOverflowError.
	 * @throws Exception If the StringTextifier fails to execute
	 * @throws StackOverflowError If the StringTextifier calls this method.
	 * @since 1.00.00
	 */
	public Context write(Number number) throws Exception {

		if (this.style == null || this.style.number().isEmpty()) {
			this.buffer.append( number );
		}
		else {
			String str = this.styler.style(number,this.style);
			this.controller.execute(str, this);
		}
		return this;

	}
	//**********************************************************************************************
	/**
	 * Writes a quoted number based on String.valueOf method. If any style is present, the number will be
	 * styled as string. Since StringTextifier will be called, StringTextifier should never call
	 * this method, or an infinity recursion will start, ending in a java.lang.StackOverflowError.
	 * @throws Exception If the StringTextifier fails to execute
	 * @throws StackOverflowError If the StringTextifier calls this method.
	 * @since 1.00.00
	 */
	public Context writeQuoted(Number number) throws Exception {

		this.buffer.append('"');
		this.write(number);
		this.buffer.append('"');
		return this;

	}
	//**********************************************************************************************
	/**
	 * Returns the textification result, cleaning the buffer for new textifications.
	 * @return The text representation of the object, like json, nvp, xml, etc.
	 * @since 1.00.00
	 */
	public String readAndClear() {

		String result = this.buffer.toString();
		this.buffer.setLength(0);
		this.stack.clear();
		return result;

	}
	//**********************************************************************************************
	/**
	 * Stacks an object, avoiding to textify it again causing dead-lock cycles.
	 * This method is protected to ensure it will be used in the package only.
	 * @param object The object to be stacked.
	 * @since 1.00.00
	 */
	protected void stack(Object object) {
		this.stack.add(0, object);
	}
	//**********************************************************************************************
	/**
	 * Removes an object from the stack.
	 * This method is protected to ensure it will be used in the package only.
	 * @since 1.00.00
	 */
	protected void unstack() {
		this.stack.remove(0);
	}
	//**********************************************************************************************
	/**
	 * Checks if the object is stacked. To so, objects are compared with the equals() method.
	 * Since StackTextifier is never applied to Primitive Textifiers and Null Textifiers, the stack
	 * will never compare null or primitive types.
	 * This method is protected to ensure it will be used in the package only.
	 * @since 1.00.00
	 */
	protected boolean isStacked(Object o) {

		for (Object o2 : this.stack) {
			if ( o2 == o ) return true;
			//if ( o2 == o || o2.equals(o) ) return true;
		}
		return false;

	}
	//**********************************************************************************************

}
